﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Net;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Threading;
using System.Windows;
using System.ServiceModel;

namespace PresentItNow
{
    public class ViewModel : PropertyChangedBase
    {
        public ViewModel(Window mainWindow)
        {
            this.mainWindow = mainWindow;
            AsyncExecutor = new AsyncExecutor(DefaultExceptionHandler);

            StartHost = new CommandBinding(Commands.StartHost, StartHost_Executed, StartHost_CanExecute);
            StopHost = new CommandBinding(Commands.StopHost, StopHost_Executed, StopHost_CanExecute);
            StartClient = new CommandBinding(Commands.StartClient, StartClient_Executed, StartHost_CanExecute);
            Connect = new CommandBinding(Commands.Connect, Connect_Executed, Connect_CanExecute);
            Disconnect = new CommandBinding(Commands.Disconnect, Disconnect_Executed, Disconnect_CanExecute);


            //marker = ShowMarker(Name);
        }
        private Window mainWindow;

        public AsyncExecutor AsyncExecutor { get; set; }

        private void DefaultExceptionHandler(Exception ex)
        {
            MessageBox.Show("Problem occurred. Details: " + ex.ToString(), "Unexpected Exception");
        }

        #region Property: string Name
        /// <summary>
        /// Holder for Name
        /// </summary>
        private string name;

        /// <summary>
        /// Get and set Name
        /// </summary>
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                FirePropertyChanged("Name");
            }
        }
        #endregion


        #region Property: string WaitMessage
        /// <summary>
        /// Holder for WaitMessage
        /// </summary>
        private string waitMessage;

        /// <summary>
        /// Get and set WaitMessage
        /// </summary>
        public string WaitMessage
        {
            get { return waitMessage; }
            set
            {
                waitMessage = value;
                FirePropertyChanged("WaitMessage");
            }
        }
        #endregion


        #region Property: OperationMode Mode
        /// <summary>
        /// Holder for Mode
        /// </summary>
        private OperationMode mode;

        /// <summary>
        /// Get and set Mode
        /// </summary>
        public OperationMode CurrentMode
        {
            get { return mode; }
            set
            {
                mode = value;
                
                FirePropertyChanged("CurrentMode");
            }
        }
        #endregion




        #region Start

        #region CommandBinding: StartHost
        public CommandBinding StartHost { get; set; }

        private void StartHost_Executed(object sender, ExecutedRoutedEventArgs args)
        {
            AsyncExecutor.StartExecution(Async_StartHosting());
        }

        private void StartHost_CanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = !string.IsNullOrEmpty(Name);
        }
        #endregion


        #region CommandBinding: StartClient
        public CommandBinding StartClient { get; set; }

        private void StartClient_Executed(object sender, ExecutedRoutedEventArgs args)
        {
            StartClient_Disconnected();
        }
       

        private void StartClient_CanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = !string.IsNullOrEmpty(Name);
        }
        #endregion
			
			
        #endregion

        #region Hosting

        Host host = null;

        private IEnumerator<SwitchExecutionContext> Async_StartHosting()
        {
            yield return SwitchExecutionContext.ToUIThread;
            CurrentMode = OperationMode.Host;

            FillIPAddresses();
            WaitMessage = "Starting Host...";

            yield return SwitchExecutionContext.ToWorkerThread;
            host = new Host();
            bool hostStarted = false;
            bool hostStartFailed = false;
            Exception hostStartError = null;
            host.HostingStartedSuccessfully += () => { hostStarted = true; };
            host.UnableToHost += ex => { hostStartError = ex; hostStartFailed = true; };

            host.StartHosting();

            while (!hostStarted || hostStartFailed)
            {
                Thread.Sleep(50);
            }

            yield return SwitchExecutionContext.ToUIThread;
            if (hostStartFailed)
            {
                MessageBox.Show("Unable to start host.\r\n\r\nDetails: " + hostStartError.ToString(), "Unable to host", MessageBoxButton.OK, MessageBoxImage.Error);
                CurrentMode = OperationMode.Start;
                yield break;
            }
            

            //Setup Markers
            marker = ShowMarker(Name, 3);
            
            setHostMarkerTimer = new DispatcherTimer();
            setHostMarkerTimer.Interval = new TimeSpan(0, 0, 0, 0, 50);
            setHostMarkerTimer.Tick += new EventHandler(timer_Tick);
            setHostMarkerTimer.Start();

            setClientMarkerTimer = new DispatcherTimer();
            setClientMarkerTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            setClientMarkerTimer.Tick += (o, args) => SetClientMarkers();
            setClientMarkerTimer.Start();

            //Create another thread to do the constant reading of the screen
            Thread t = new Thread(new ThreadStart(ReadScreen));
            t.IsBackground = true;
            t.Start();
        }

        public event Func<string, int, MarkerWindow> ShowMarker;

        DispatcherTimer setHostMarkerTimer = null;

        DispatcherTimer setClientMarkerTimer = null;

        MarkerWindow marker = null;

        void timer_Tick(object sender, EventArgs e)
        {
            System.Windows.Point pos = CursorHelper.GetCursorPos(mainWindow);
            marker.Left = pos.X - 10;
            marker.Top = pos.Y - 10;
        }

        private bool stopped = false;

        private void ReadScreen()
        {
            while (!stopped)
            {
                //Wrapping it in a try catch block because some of the Bitmap operations fail from time to time
                try
                {
                    StringBuilder log = new StringBuilder();
                    DateTime started = DateTime.Now;

                    log.AppendLine("SetClientMarkers took: " + DateTime.Now.Subtract(started).TotalMilliseconds);
                    started = DateTime.Now;

                    int width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
                    int height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;

                    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(width, height);
                    System.Drawing.Graphics gfx = System.Drawing.Graphics.FromImage(bmp);

                    int sourceX = System.Windows.Forms.Screen.PrimaryScreen.Bounds.X;
                    int sourceY = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Y;
                    gfx.CopyFromScreen(sourceX, sourceY, 0, 0, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Size, System.Drawing.CopyPixelOperation.SourceCopy);

                    log.AppendLine("CopyFromScreen took: " + DateTime.Now.Subtract(started).TotalMilliseconds);
                    started = DateTime.Now;

                    host.LastPictureBuffer = bmp;
                }
                catch { }
                
                //Give it some air to breath...
                Thread.Sleep(30);
            }
        }


        Dictionary<string, MarkerWindow> clientMarkers = new Dictionary<string, MarkerWindow>();

        private void SetClientMarkers()
        {
            List<ClientInfo> clientInfos = host.GetClientInfos();

            foreach (ClientInfo info in clientInfos)
            {
                MarkerWindow clientMarker = null;
                if (!clientMarkers.TryGetValue(info.Name, out clientMarker))
                {
                    clientMarker = ShowMarker(info.Name, clientMarkers.Count);
                    clientMarkers[info.Name] = clientMarker;
                    clientMarker.Show();
                }

                try
                {
                    //Wrapped in a try catch block if the position that comes from the client is nonsense (e.g. Infinity etc.)
                    clientMarker.Left = info.PointToX - 10;
                    clientMarker.Top = info.PointToY - 10;
                }
                catch { }
            }
        }

        private void FillIPAddresses()
        {
            IPAddress[] addresses = Dns.GetHostAddresses(Dns.GetHostName());
            List<string> ips = new List<string>();
            foreach (IPAddress address in addresses)
            {
                ips.Add(address.ToString());
            }
            HostIPs = ips;
        }


        #region Property: List<string> HostIPs
        /// <summary>
        /// Holder for HostIPs
        /// </summary>
        private List<string> hostIPs;

        /// <summary>
        /// Get and set HostIPs
        /// </summary>
        public List<string> HostIPs
        {
            get { return hostIPs; }
            set
            {
                hostIPs = value;
                FirePropertyChanged("HostIPs");
            }
        }
        #endregion



        #region CommandBinding: StopHost
        public CommandBinding StopHost { get; set; }

        private void StopHost_Executed(object sender, ExecutedRoutedEventArgs args)
        {
            host.StopHosting();
            Environment.Exit(0);
        }

        private void StopHost_CanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = (CurrentMode == OperationMode.Host);
        }
        #endregion
			


        #endregion


        #region Client_Disconnected

        #region Property: string HostIP
        /// <summary>
        /// Holder for HostIP
        /// </summary>
        private string hostIP;

        /// <summary>
        /// Get and set HostIP
        /// </summary>
        public string HostIP
        {
            get { return hostIP; }
            set
            {
                hostIP = value;
                FirePropertyChanged("HostIP");
            }
        }
        #endregion

        private void StartClient_Disconnected()
        {
            CurrentMode = OperationMode.Client_Disconnected;
        }

        #region CommandBinding: Connect
        public CommandBinding Connect { get; set; }

        private void Connect_Executed(object sender, ExecutedRoutedEventArgs args)
        {
            AsyncExecutor.StartExecution(Async_ConnectClient());
        }

        private void Connect_CanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = !string.IsNullOrEmpty(HostIP);
        }
        #endregion
			

        #endregion

        #region Client_Connected

        IContract channel = null;
        private IEnumerator<SwitchExecutionContext> Async_ConnectClient()
        {
            yield return SwitchExecutionContext.ToUIThread;
            WaitMessage = "Connecting to " + HostIP + "...";

            yield return SwitchExecutionContext.ToWorkerThread;
            string errorText = null;
            try
            {
                System.ServiceModel.Channels.Binding binding = Utils.CreateBinding();

                string address = string.Format("net.tcp://{0}:31415/ScreenShare", HostIP);

                ChannelFactory<IContract> factory = new ChannelFactory<IContract>(binding, address);
                channel = factory.CreateChannel();
                channel.Ping();
            }
            catch (Exception ex)
            {
                errorText = ex.ToString();
            }
            yield return SwitchExecutionContext.ToUIThread;
            if (errorText != null)
            {
                MessageBox.Show("Unable to connect.\r\n\r\nDetails: " + errorText, "Connect problem", MessageBoxButton.OK, MessageBoxImage.Error);
                yield break;
            }

            CurrentMode = OperationMode.Client_Connected;

            viewer = new Viewer(channel,Name);
            //viewer.Owner = mainWindow;
            viewer.ImageQuality = Quality;            

            viewer.Show();

            viewer.Closed += new EventHandler(viewer_Closed);
        }

        void viewer_Closed(object sender, EventArgs e)
        {
            Disconnect_Executed(this, null);
        }


        #region Property: double Quality
        /// <summary>
        /// Holder for Quality
        /// </summary>
        private double quality = 30;

        /// <summary>
        /// Get and set Quality
        /// </summary>
        public double Quality
        {
            get { return quality; }
            set
            {
                quality = value;
                if (viewer != null)
                    viewer.ImageQuality = quality;
                FirePropertyChanged("Quality");
            }
        }
        #endregion


        Viewer viewer = null;

        #region CommandBinding: Disconnect
        public CommandBinding Disconnect { get; set; }

        private void Disconnect_Executed(object sender, ExecutedRoutedEventArgs args)
        {
            Environment.Exit(0);
        }

        private void Disconnect_CanExecute(object sender, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = (CurrentMode == OperationMode.Client_Connected);
        }
        #endregion
			
        #endregion


        
    }

    public enum OperationMode
    {
        Start,
        Host,
        Client_Disconnected,
        Client_Connected
    }

    
}
